├── .gitignore
├── README.md
├── acf-svg-icon.php
├── assets
├── css
│ ├── style.css
│ └── style.min.css
└── js
│ ├── input-5.js
│ ├── input-5.min.js
│ ├── input-56.js
│ └── input-56.min.js
├── composer.json
├── fields
├── acf-5.php
├── acf-56.php
└── acf-base.php
├── gulpfile.js
├── languages
├── acf-svg-icon-fr_FR.mo
├── acf-svg-icon-fr_FR.po
└── acf-svg-icon.pot
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # folders and files to be ignored by git
2 |
3 | ############
4 | ## IDEs
5 | ############
6 |
7 | *.pydevproject
8 | .project
9 | .metadata
10 | *.swp
11 | *~.nib
12 | local.properties
13 | .classpath
14 | .settings/
15 | .loadpath
16 | .externalToolBuilders/
17 | *.launch
18 | .cproject
19 | .buildpath
20 | nbproject/
21 | node_modules/*
22 | premium/node_modules/*
23 | package-lock.json
24 |
25 | ############
26 | ## OSes
27 | ############
28 |
29 | [Tt]humbs.db
30 | [Dd]esktop.ini
31 | *.DS_store
32 | .DS_store?
33 |
34 | ############
35 | ## Misc
36 | ############
37 |
38 | bin/
39 | tmp/
40 | *.tmp
41 | *.bak
42 | *.log
43 | *.[Cc]ache
44 | *.cpr
45 | *.orig
46 | *.php.in
47 | .idea/
48 | temp/
49 | ._*
50 | .Trashes
51 |
52 | .svn
53 |
54 | ############
55 | ## Composer
56 | ############
57 | vendor/
58 | composer.phar
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Advanced Custom Fields: SVG Icon #
2 |
3 | This enhance [Advanced Custom Field](https://www.advancedcustomfields.com/pro/) plugin by adding a custom field.
4 |
5 | This ACF field is a select2 field in order to include your great fonts. It will allow you to select icons and then return the corresponding class icon.
6 |
7 | ## Compatibility
8 |
9 | This ACF field type is compatible with:
10 | * ACF 5.0.0 and up, that means the pro version.
11 | * ACF 4 (not supported).
12 |
13 | ## Installation
14 |
15 | ### via Composer
16 |
17 | 1. Add a line to your repositories array: `{ "type": "git", "url": "https://github.com/BeAPI/acf-svg-icon" }`
18 | 2. Add a line to your require block: `"bea/acf-svg-icon": "dev-master"`
19 | 3. Run: `composer update`
20 |
21 | ### Manual
22 |
23 | 1. Copy the plugin folder into your plugins folder.
24 | 2. Activate the plugin via the plugins admin page.
25 | 3. Create a new field via ACF and select the SVG Icon selector.
26 |
27 | ## How to ##
28 |
29 | ### Upload SVG into library
30 |
31 | You can upload media in your library, it must be an SVG, and then it will be displayed into the SVG dropdown.
32 | In this case, consider using [Scalable Vector Graphics (svg)](https://fr.wordpress.org/plugins/scalable-vector-graphics-svg) for security.
33 |
34 | ### In your own theme ###
35 |
36 | To load several SVGs from your theme (development), use the following filter to add the main sprite SVG file :
37 |
38 | ```php
39 | version property undefined on ACF versions under 5.6
89 | * use built-in wrapper acf_get_setting('version') to retrieve version
90 |
91 | ### 1.2.0 - 27 July 2017
92 | * Add compatibility for ACF 5.6.0 and more versions
93 | * Still keep compatibility for ACF 5.6.0 and lower versions
94 | * Add some custom CSS for a more beautiful admin UI
95 | * Now displaying the icon name, not anymore like a slug
96 | * Improve readme
97 |
98 | ### 1.0.1 - 11 May 2017
99 | * Initial
100 |
--------------------------------------------------------------------------------
/acf-svg-icon.php:
--------------------------------------------------------------------------------
1 | =' ) ) {
71 | // Register field for ACF 5.6 or greater
72 | include_once sprintf( '%sfields/acf-base.php', ACF_SVG_ICON_DIR );
73 | include_once sprintf( '%sfields/acf-56.php', ACF_SVG_ICON_DIR );
74 |
75 | acf_register_field_type( 'acf_field_svg_icon_56' );
76 | } else {
77 | // Register field for ACF 5
78 | include_once sprintf( '%sfields/acf-base.php', ACF_SVG_ICON_DIR );
79 | include_once sprintf( '%sfields/acf-5.php', ACF_SVG_ICON_DIR );
80 |
81 | $klass = 'acf_field_svg_icon_5';
82 | new $klass();
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * Init plugin.
89 | *
90 | * @since 1.0.0
91 | */
92 | function acf_field_svg_icon() {
93 | new acf_field_svg_icon_plugin();
94 | }
95 |
96 | add_action( 'plugins_loaded', 'acf_field_svg_icon' );
97 |
98 |
--------------------------------------------------------------------------------
/assets/css/style.css:
--------------------------------------------------------------------------------
1 | .select2-results svg,
2 | .acf_svg__icon {
3 | width:40px;
4 | height:40px;
5 | margin-right: 10px;
6 | vertical-align: middle;
7 | }
8 |
9 | .select2-selection svg,
10 | .acf_svg__icon.small {
11 | width: 25px;
12 | height: 19px;
13 | margin-right: 10px;
14 | margin-top: 3px;
15 | float: left;
16 | size: 2px;
17 | }
18 |
19 | .acf_svg__span {
20 | font-size: 1.2em;
21 | }
--------------------------------------------------------------------------------
/assets/css/style.min.css:
--------------------------------------------------------------------------------
1 | .select2-results svg,.acf_svg__icon{width:40px;height:40px;margin-right:10px;vertical-align:middle}.select2-selection svg,.acf_svg__icon.small{width:25px;height:19px;margin-right:10px;margin-top:3px;float:left;size:2px}.acf_svg__span{font-size:1.2em}
--------------------------------------------------------------------------------
/assets/js/input-5.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | function initialize_field($field) {
3 | var input = $field.find('.acf-input input');
4 | var allowClear = $(input).attr('data-allow-clear') || 0;
5 | var opts = {
6 | dropdownCssClass: "bigdrop widefat",
7 | dropdownAutoWidth: true,
8 | formatResult: svg_icon_format,
9 | formatSelection: svg_icon_format_small,
10 | data: {results: svg_icon_format_data},
11 | allowClear: 1 == allowClear
12 | };
13 |
14 | input.select2(opts);
15 |
16 | async function fetchSvg(url, id, text) {
17 | var req = await fetch(url);
18 | var svg = await req.text();
19 | $('span[data-id="' + id + '"]').html(svg + text);
20 | }
21 |
22 | /**
23 | * Format the content in select 2 for the selected item
24 | *
25 | * @param css
26 | * @returns {string}
27 | */
28 | function svg_icon_format(css) {
29 | if (!css.id) {
30 | return css.text;
31 | }
32 | var id = css.id;
33 |
34 | if (css.url) {
35 | fetchSvg(css.url, id, css.text);
36 | return $('' + css.text + '');
37 | } else {
38 | return $('' + css.text);
39 | }
40 | };
41 |
42 | /**
43 | * Format the content in select 2 for the dropdown list
44 | *
45 | * @param css
46 | * @returns {string}
47 | */
48 | function svg_icon_format_small(css) {
49 | if (!css.id) {
50 | return css.text;
51 | }
52 | var id = css.id;
53 |
54 | if (css.url) {
55 | fetchSvg(css.url, id, css.text);
56 | return $('' + css.text + '');
57 | } else {
58 | return $('' + css.text );
59 | }
60 | };
61 | }
62 |
63 | /*
64 | * ready append (ACF5)
65 | *
66 | * These are 2 events which are fired during the page load
67 | * ready = on page load similar to jQuery(document).ready()
68 | * append = on new DOM elements appended via repeater field
69 | *
70 | * @type event
71 | * @date 20/07/13
72 | *
73 | * @param jQueryel (jQuery selection) the jQuery element which contains the ACF fields
74 | * @return n/a
75 | */
76 | acf.add_action('ready append', function (jQueryel) {
77 | // search jQueryel for fields of type 'FIELD_NAME'
78 | acf.get_fields({type: 'svg_icon'}, jQueryel).each(function () {
79 | initialize_field($(this));
80 | });
81 | });
82 | })(jQuery);
--------------------------------------------------------------------------------
/assets/js/input-5.min.js:
--------------------------------------------------------------------------------
1 | !function(t){acf.add_action("ready append",(function(a){acf.get_fields({type:"svg_icon"},a).each((function(){!function(a){var n=a.find(".acf-input input"),i=t(n).attr("data-allow-clear")||0,s={dropdownCssClass:"bigdrop widefat",dropdownAutoWidth:!0,formatResult:function(a){if(!a.id)return a.text;var n=a.id;return a.url?(e(a.url,n,a.text),t(''+a.text+"")):t(''+a.text)},formatSelection:function(a){if(!a.id)return a.text;var n=a.id;return a.url?(e(a.url,n,a.text),t(''+a.text+"")):t(''+a.text)},data:{results:svg_icon_format_data},allowClear:1==i};async function e(a,n,i){var s=await fetch(a),e=await s.text();t('span[data-id="'+n+'"]').html(e+i)}n.select2(s)}(t(this))}))}))}(jQuery);
--------------------------------------------------------------------------------
/assets/js/input-56.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | function initialize_field($field) {
3 | var input = $field.find('.acf-input input');
4 | var allowClear = $(input).attr('data-allow-clear') || 0;
5 | var opts = {
6 | dropdownCssClass: "bigdrop widefat",
7 | dropdownAutoWidth: true,
8 | templateResult: bea_svg_format,
9 | templateSelection: bea_svg_format_small,
10 | data: svg_icon_format_data,
11 | allowClear: 1 == allowClear,
12 | };
13 |
14 | input.select2(opts);
15 |
16 | async function fetchSvg(url, id, text) {
17 | var req = await fetch(url);
18 | var svg = await req.text();
19 | $('span[data-id="' + id + '"]').html(svg + text);
20 | }
21 |
22 | /**
23 | * Format the content in select 2 for the selected item
24 | *
25 | * @param css
26 | * @returns {string}
27 | */
28 | function bea_svg_format(css) {
29 | if (!css.id) {
30 | return css.text;
31 | }
32 |
33 | var id = css.id;
34 |
35 | if (css.url) {
36 | fetchSvg(css.url, id, css.text);
37 | return $('' + css.text + '');
38 | } else {
39 | return $('' + css.text + '');
40 | }
41 | };
42 |
43 | /**
44 | * Format the content in select 2 for the dropdown list
45 | *
46 | * @param css
47 | * @returns {string}
48 | */
49 | function bea_svg_format_small(css) {
50 | if (!css.id) {
51 | return css.text;
52 | }
53 |
54 | var id = css.id;
55 |
56 | if (css.url) {
57 | fetchSvg(css.url, id, css.text);
58 | return $('' + css.text + '');
59 | } else {
60 | return $('' + css.text + '');
61 | }
62 | };
63 | }
64 |
65 | /*
66 | * ready append (ACF5)
67 | *
68 | * These are 2 events which are fired during the page load
69 | * ready = on page load similar to jQuery(document).ready()
70 | * append = on new DOM elements appended via repeater field
71 | *
72 | * @type event
73 | * @date 20/07/13
74 | *
75 | * @param jQueryel (jQuery selection) the jQuery element which contains the ACF fields
76 | * @return n/a
77 | */
78 | acf.add_action('ready append', function (jQueryel) {
79 | // search jQueryel for fields of type 'FIELD_NAME'
80 | acf.get_fields({type: 'svg_icon'}, jQueryel).each(function () {
81 | initialize_field($(this));
82 | });
83 | });
84 | })(jQuery);
--------------------------------------------------------------------------------
/assets/js/input-56.min.js:
--------------------------------------------------------------------------------
1 | !function(a){acf.add_action("ready append",(function(t){acf.get_fields({type:"svg_icon"},t).each((function(){!function(t){var n=t.find(".acf-input input"),s=a(n).attr("data-allow-clear")||0,i={dropdownCssClass:"bigdrop widefat",dropdownAutoWidth:!0,templateResult:function(t){if(!t.id)return t.text;var n=t.id;return t.url?(e(t.url,n,t.text),a(''+t.text+"")):a(''+t.text+"")},templateSelection:function(t){if(!t.id)return t.text;var n=t.id;return t.url?(e(t.url,n,t.text),a(''+t.text+"")):a(''+t.text+"")},data:svg_icon_format_data,allowClear:1==s};async function e(t,n,s){var i=await fetch(t),e=await i.text();a('span[data-id="'+n+'"]').html(e+s)}n.select2(i)}(a(this))}))}))}(jQuery);
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bea/acf-svg-icon",
3 | "description": "Add ACF field for selecting SVG icons.",
4 | "minimum-stability": "stable",
5 | "license": "GPL-2.0",
6 | "authors": [
7 | {
8 | "name": "BeAPI",
9 | "email": "technique@beapi.fr"
10 | }
11 | ],
12 | "type": "wordpress-plugin"
13 | }
--------------------------------------------------------------------------------
/fields/acf-5.php:
--------------------------------------------------------------------------------
1 | name = 'svg_icon';
17 | $this->label = __( 'SVG Icon selector', 'acf-svg-icon' );
18 | $this->category = __( 'Basic', 'acf' );
19 | $this->defaults = array(
20 | 'allow_clear' => 0,
21 | );
22 |
23 | // do not delete!
24 | parent::__construct();
25 |
26 | // Hooks !
27 | add_action( 'save_post_attachment', array( $this, 'save_post_attachment' ) );
28 | }
29 |
30 | /**
31 | *
32 | * Create the HTML interface for your field
33 | *
34 | * @param $field - an array holding all the field's data
35 | *
36 | * @type action
37 | * @since 3.6
38 | * @date 23/01/13
39 | */
40 | public function render_field( $field ) {
41 | ?>
42 |
46 | __( 'Display clear button?', 'acf-svg-icon' ),
67 | 'instructions' => __( 'Whether or not a clear button is displayed when the select box has a selection.', 'acf-svg-icon' ),
68 | 'name' => 'allow_clear',
69 | 'type' => 'true_false',
70 | 'ui' => 1,
71 | ) );
72 | }
73 |
74 | /**
75 | * Get the SVG filepath from theme.
76 | *
77 | * @return array
78 | * @author Nicolas JUEN
79 | */
80 | private function get_svg_files_path() {
81 | $custom_svg_path_icons = apply_filters( 'acf_svg_icon_filepath', array() );
82 |
83 | return array_map( function ( $val ) {
84 | return [
85 | 'type' => 'custom',
86 | 'file' => $val,
87 | ];
88 | }, (array) $custom_svg_path_icons );
89 | }
90 |
91 | /**
92 | * Merge WP Medias SVG and custom SVG files
93 | *
94 | * @return array
95 | * @since 2.0.0
96 | *
97 | */
98 | public function get_all_svg_files() {
99 | // First try to load files list from the cache.
100 | $files = get_transient( $this->cache_key );
101 | if ( ! empty( $files ) ) {
102 | return $files;
103 | }
104 |
105 | /**
106 | * Get WP Media SVGs
107 | *
108 | * @since 2.0.0
109 | */
110 | $media_svg_files = $this->get_medias_svg();
111 |
112 | /**
113 | * The path to the svg file.
114 | *
115 | * @since 1.0.0
116 | */
117 | $custom_svg_files = $this->get_svg_files_path();
118 |
119 | $files = array_merge( $media_svg_files, $custom_svg_files );
120 |
121 | // Cache 24 hours.
122 | set_transient( $this->cache_key, $files, HOUR_IN_SECONDS * 24 );
123 |
124 | return $files;
125 | }
126 |
127 | /**
128 | * Extract icons from svg file.
129 | *
130 | * @return array|bool
131 | * @since 1.0.0
132 | *
133 | */
134 | public function parse_svg() {
135 | $files = $this->get_all_svg_files();
136 | if ( empty( $files ) ) {
137 | return false;
138 | }
139 |
140 | /**
141 | * Get the allowed tags to parse icon's ids
142 | *
143 | * @param string $allowed_tags : Passed directly to strip_tags
144 | *
145 | * @return string
146 | * @author david-treblig
147 | * @since 2.0.1
148 | *
149 | */
150 | $allowed_tags = apply_filters( 'acf_svg_icon_svg_parse_tags', '' );
151 |
152 | $out = array();
153 |
154 | // Ignore SVG with type media to check if there are multiple sprite
155 | $custom_files = array_filter(
156 | $files,
157 | function ( $file ) {
158 | return 'media' !== $file['type'];
159 | }
160 | );
161 |
162 | foreach ( $files as $file ) {
163 | if ( ! is_file( $file['file'] ) ) {
164 | continue;
165 | }
166 |
167 | if ( 'media' === $file['type'] ) {
168 | $pathinfo = pathinfo( $file['file'] );
169 | $out[] = array(
170 | 'id' => $file['id'],
171 | 'text' => self::get_nice_display_text( $pathinfo['filename'], false ),
172 | 'url' => $file['file_url'],
173 | 'disabled' => false,
174 | );
175 | } else {
176 | // If not extract them from the CSS file.
177 | $contents = file_get_contents( $file['file'] );
178 | preg_match_all( '/id="(\S+)"/m', strip_tags( $contents, $allowed_tags ), $svg );
179 |
180 | foreach ( $svg[1] as $id ) {
181 | $id = sanitize_title( $id );
182 | // If multiple sprites registered, return sprite name and icon name, otherwise return icon name only
183 | $value = 1 < count( $custom_files ) ? basename( $file['file'] ) . '#' . $id : $id;
184 | $out[] = array(
185 | 'id' => $value,
186 | 'text' => self::get_nice_display_text( $id ),
187 | 'disabled' => false,
188 | );
189 | }
190 | }
191 | }
192 |
193 | return apply_filters( 'acf_svg_icon_parsed_svg', $out, $files );
194 | }
195 |
196 | /**
197 | * Get WP Medias SVGs
198 | *
199 | * @return array
200 | * @since 2.0.0
201 | *
202 | */
203 | public function get_medias_svg() {
204 | $args = array(
205 | 'post_type' => 'attachment',
206 | 'posts_per_page' => '-1',
207 | 'post_status' => 'inherit',
208 | 'post_mime_type' => 'image/svg+xml',
209 | );
210 |
211 | /**
212 | * Filter WP Query get attachments args
213 | *
214 | * @param array $args
215 | *
216 | * @since 2.0.0
217 | *
218 | */
219 | $args = apply_filters( 'acf_svg_icon_wp_medias_svg_args', $args );
220 |
221 | $attachments = new WP_Query( $args );
222 | if ( empty( $attachments->posts ) ) {
223 | return array();
224 | }
225 |
226 | $svg = array();
227 | foreach ( $attachments->posts as $attachment ) {
228 | $svg[] = [
229 | 'type' => 'media',
230 | 'id' => $attachment->ID,
231 | 'file' => get_attached_file( $attachment->ID ),
232 | 'file_url' => wp_get_attachment_url( $attachment->ID ),
233 | ];
234 | }
235 |
236 | return $svg;
237 | }
238 |
239 | /**
240 | * Format the icon id to get his nicename for display purpose
241 | *
242 | * @param $id
243 | * @param bool $delete_suffix
244 | *
245 | * @return string
246 | * @since 1.2.0
247 | */
248 | public static function get_nice_display_text( $id, $delete_suffix = true ) {
249 | // Split up the string based on the '-' carac
250 | $ex = explode( '-', $id );
251 | if ( empty( $ex ) ) {
252 | return $id;
253 | }
254 |
255 | // Delete the first value, as it has no real value for the icon name.
256 | if ( $delete_suffix ) {
257 | unset( $ex[0] );
258 | }
259 |
260 | // Remix values into one with spaces
261 | $text = implode( ' ', $ex );
262 |
263 | // Add uppercase to the first word
264 | return ucfirst( $text );
265 | }
266 |
267 | /**
268 | * Display the css based on the vars given for dynamic fonts url.
269 | *
270 | * @since 1.0.0
271 | */
272 | public function display_svg() {
273 | /**
274 | * The svg's files URLs
275 | *
276 | * @param array $font_urls the default svg file url
277 | *
278 | * @since 1.0.0
279 | *
280 | */
281 | $files = $this->get_all_svg_files();
282 | if ( empty( $files ) ) {
283 | return;
284 | }
285 |
286 | foreach ( $files as $file ) {
287 | // Ignore file type "media" because we use the URL and not the svg embeded.
288 | if ( 'media' === $file['type'] || ! is_file( $file['file'] ) ) {
289 | continue;
290 | }
291 |
292 | $svg = file_get_contents( $file['file'] );
293 |
294 | if ( true === strpos( $svg, 'style="' ) ) {
295 | $svg = str_replace( 'style="', 'style="display:none; ', $svg );
296 | } else {
297 | $svg = str_replace( '