Wordpress Plugin длиной в одну страницу

    Почему люди любят Wordpress? Потому что с ним просто работать. В нём нет гибкости большущих CMS вроде Joomla и Drupal, — а значит, не запутаешься. И ещё он очень популярен — а значит, можно найти плагины на все случаи жизни.

    Неспроста несмотря на осуждение со стороны Lurkmore.ru, Wordpress-ом пользуются и Герб Саттер, и Марк Шаттлворт, и много кто ещё. Например, я.

    В своих записях я очень часто ссылаюсь на чужие блоги. И мне пришла идея — а почему бы не показывать рядом с ником человека, на которого я ссылаюсь, ещё и значок его сервиса? Например, птичку из твиттера или букву B из блогспота? Похожий функционал есть, например, в Википедии, да и многие блогохостинги это позволяют (например, Dreamwidth).

    Так и родился плагин для Wordpress Rikki's WP Social Icons. Позволяет за один клик мышкой добавить ссылку на эккаунт в каком-нибудь сервисе, от социальной сети до GitHub.

    Зачем?


    Есть много статей о том, как правильно писать plugin-ы для Wordpress, но не в одной из них не было ни слова про те баги, с которыми я столкнулся в процессе работы. Поэтому и записываю свои впечатления — вдруг кто-то столкнётся с тем же, а как решить — не знает. И указываю все доработки, которые обычно опускают в учебных примерах.

    Во время работы я довольно много пользовался исходниками чужих плагинов, а также наработками Jenyay (ссылки на его цикл статей приведён в приложении). Но «кустарной» реализации было мало. Plugin тем и удобен, что каждый конечный пользователь может доработать его сам. А значит, нужно сделать так, чтобы добавить к нему поддержку нового сервиса было делом пяти минут и одной переустановки.

    Особенно помогало работе то, что я собирался использовать этот плагин сам (т.е., питаться собачьим кормом, как говорят наши американские коллеги). И именно мои мучения с первыми набросками подсказали мне, где и что можно улучшить.

    Программируем


    Проектируем


    Начнём с того, что писать плагины для Wordpress не просто, а очень просто. Не нужно создавать ни классов, от чего-то там унаследованных, ни мудрить с форматами, ни следовать каким-то хитрым спецификациям. «События» вынесены в специальные объекты, которые работают примерно как event-ы в «больших» приложениях.

    Для начала надо хорошенько обдумать, что нам нужно. А нужен нам способ маркировки отдельных слов, который был бы виден при редактировании, и автоматически переделывался в ссылку в постах, страницах и RSS.

    Очень похоже работает shortcode — специальный тег, окружённый квадратными скобками и специально заточенные под обработку плагинами. Они появились ещё версии 2.5, которая вышла в далёком 2008 году, так особые проблемы с совместимостью нам не грозят.

    Какой формат нам следует принимать от пользователя? Т.к. конечный HTML-код будет отличаться только иконкой и url-ом, было бы логично завести один shortcode и одну функцию, которая бы его обрабатывала. Дополнительные сведения можно передавать и через параметры, что очень удобно. К тому же, чем меньше shortcode-ов мы создадим, тем меньше шанс, что мы вступим в конфликт с каким-то другим плагином.

    Мой shortcode выглядел так:

    [userid]

    Его параметры:

    'id' — необязательный параметр. id, под которым наш герой зарегистрирован на сервисе. Например. torvalds-family.

    'type'- сервис

    'url'- необязательный параметр. url на который мы хотим сослаться (например, профиль или какой-то отдельный пост в блоге).

    А использовать его вот так:

    [userid type="blogspot" id="torvalds-family"]Linus Torvalds[/userid]


    или так:

    [userid type="blogspot" url="http://blogspot.com"]blogspot[/userid]


    Пишем ядро



    Теперь создаём структуру для нашего плагина (см. на GitHub) и набрасываем в rikkis-wp-social-icons.php наше миниатюрное ядро:

    class socialusers
    {
    	var $options = array(
    		"blogspot" => "http://%s.blogspot.com/",
    		"ljuser" => "http://%s.livejournal.com/",
    		"ljcomm" => "http://livejournal.com/community/%s",
    		"liruboy" => "http://www.liveinternet.ru/users/%s/",
    		"lirugirl" => "http://www.liveinternet.ru/users/%s/",
    		"vk" => "http://vk.com/%s",
    		"twitter" => "http://twitter.com/#!/%s/",
    		"facebook" => "http://www.facebook.com/%s",
    		"google_plus" => "https://plus.google.com/%s",
    		"wordpress" => "http://%s.wordpress.com/",
    		"habrahabr" => "http://%s.habrahabr.ru/",
    		"github" => "http://github.com/users/%s/"
    	);
    	function socialusers(){
    		if (!function_exists ('add_shortcode') ) return;
    		add_shortcode('userid', array (&$this, 'icon_func') );
    	}
    	function icon_func($atts, $content="") {
    		if (!$content)	return "";
    		extract( shortcode_atts ( array('id' => null, 'type' => null, 'url' => null), $atts ) );
    		if (!$type || !array_key_exists($type, $this->options) )	return $content;
    		if (!$id)	$id = $content;
    		$userinfo_url = esc_url(($url) ? $url : sprintf($this->options[$type], trim($id)));
    		$userpic_url = esc_url(plugins_url( "js/img/$type.gif" , __FILE__ ));
    		return "<span style='white-space: nowrap; display: inline !important;'><a href='$userinfo_url' ref='nofollow'><img src='$userpic_url' alt='[info]' width='17' height='17' style='vertical-align: bottom; border: 0; padding-right: 1px;vertical-align:middle; margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0;' /></a><a href='$userinfo_url' ref='nofollow'><b>$content</b></a></span>";
    	}
    }
    $socialusers = new socialusers();
    


    Чтобы избежать конфликта имён переменных, мы сложили все наши вызовы в один класс socialusers. В переменной options хранятся id и URL-ы сервисов, на которые мы собираемся ссылаться, с заменой имени пользователя на %s. В папке js/img кладём gif-ки с соответствующими иконками.

    В конструкторе мы проверяем, поддерживает ли Wordpress shortcode, и, если нет, то не делаем уже ничего. Нехорошо обваливать пользователю весь Wordpress только потому, что он пользуется старой версией.

    Если всё в порядке — мы добавляем новый shortcode, указывая для него в качестве обработчика функцию icon_func из этого же экземпляра класса.

    icon_func — она очень короткая и очень интересная. В неё приходит 2 параметра:

    $atts — массив атрибутов
    $content — текст между тегами

    Пользователь увидит вместо shortcode то, что вернёт ему эта функция. Именно здесь (в последнем return) и формируется окончательный код нашего блока.

    По совету из комментариев, все пришедшие «на вход» строки проверяются через esc_url, чтобы не было дырок в безопасности.

    Заслуживают внимания две строчки:
    extract( shortcode_atts ( array('id' => null, 'type' => null, 'url' => null), $atts ) );
    </<source>
    
    формирует из элементов массива $atts локальные переменные с соответствующими именами
    и
    <source lang="php">
    plugins_url( "js/img/$type.gif" , __FILE__ );
    


    Получает url иконки по типу, относительно текущей директории. Именно для этого нужен атрибут __FILE__. Если же его нет (а авторы некоторых плагинов и примеров явно про него не слышали), то придётся вставлять имя директории плагина и всё равно это может не заработать.

    Не менее важно сделать trim() для $id. Дело в том, что если дважды щёлкнуть по слову в editor-е Wordpress-а, то он выделит слово вместе с пробелом после него. В результате мы получим имя учётной записи с совершенно неуместным пробелом и ссылка работать не будет.

    В принципе, уже в таком виде (17 строк кода + 14 строк настроек) наш плагин можно ставить и использовать. Настоящий хакер презирает оконные интерфейсы :). Вы можете попробовать запаковать php и картинки в zip и установить их в wordpress, как устанавливают обычный плагин.

    Очень рекомендую поставить для подобных экспериментов локальный Apache+PHP+mySQL, и к ним впридачу Wordpress. Отладка пойдёт намного веселее — например, вместо установки-переустановки можно будет просто подменять файлы в соответствующем каталоге.

    Добавляем кнопки



    За редактирование постов и страниц в Wordpress отвечает отдельный компонент tinyMCE. Чтобы его дёрнуть из основного PHP, нужно немного расширить нашу первоначальную форму:

    class socialusers
    {
    	var $options = array(
    		"blogspot" => "http://%s.blogspot.com/",
    		"ljuser" => "http://%s.livejournal.com/",
    		"ljcomm" => "http://livejournal.com/community/%s",
    		"liruboy" => "http://www.liveinternet.ru/users/%s/",
    		"lirugirl" => "http://www.liveinternet.ru/users/%s/",
    		"vk" => "http://vk.com/%s",
    		"twitter" => "http://twitter.com/#!/%s/",
    		"facebook" => "http://www.facebook.com/%s",
    		"google_plus" => "https://plus.google.com/%s",
    		"wordpress" => "http://%s.wordpress.com/",
    		"habrahabr" => "http://%s.habrahabr.ru/",
    		"github" => "http://github.com/users/%s/"
    	);
    	function socialusers(){
    		if (!function_exists ('add_shortcode') ) return;
    		add_shortcode('userid', array (&$this, 'icon_func') );
    		add_filter( 'mce_buttons_3', array(&$this, 'mce_buttons') );
    		add_filter( 'mce_external_plugins', array(&$this, 'mce_external_plugins') );
    	}
    	function icon_func($atts, $content="") {
    		if (!$content)
    			return "";
    		extract( shortcode_atts ( array('id' => null, 'type' => null, 'url' => null), $atts ) );
    		if (!$type || !array_key_exists($type, $this->options) )
    			return $content;
    		if (!$id)
    			$id = $content;
    		$userinfo_url = esc_url(($url) ? $url : sprintf($this->options[$type], trim($id)));
    		$userpic_url = esc_url(plugins_url( "js/img/$type.gif" , __FILE__ ));
    		return "<span style='white-space: nowrap; display: inline !important;'><a href='$userinfo_url' ref='nofollow'><img src='$userpic_url' alt='[info]' width='17' height='17' style='vertical-align: bottom; border: 0; padding-right: 1px;vertical-align:middle; margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0;' /></a><a href='$userinfo_url' ref='nofollow'><b>$content</b></a></span>";
    	}
    	function mce_external_plugins($plugin_array) {
    		$plugin_array['rikkisocialicons'] = plugins_url ('js/rikkis-wp-social-icons-editor_plugin.js', __FILE__ );
    		return $plugin_array;
    	}
    	function mce_buttons($buttons) {
    		return array_merge($buttons, array_keys($this->options));
    	}
    }
    $socialusers = new socialusers();
    


    Тут всё просто — mce_external_plugins подгружает JavaScript, в котором и будут генерировать кнопки, а mce_buttons кладёт туда все ключи из словаря options.

    Теперь нам предстоит написать JavaScript для кнопочек. Увы, но tinyMCE вшит в Wordpress настолько прочно, что стандартный способ передачи параметров из PHP в JavaScript для плагинов Wordpress здесь не сработает. Конечно, можно было бы попытаться получить их через JSon и добиться, чтобы исправление нужно было вносить действительно только в одном месте. Но это тот самый случай, когда малозначительное удобство может вызвать значительные проблемы.

    Обрамление у tinyMCE плагина довольно стандартное:

    (function() {
    	tinymce.create('tinymce.plugins.RikkiSocialIconsPlugin', {
    		init : function(ed, url) {
    			//здесь добавляем кнопки
    			}
    		},
    		getInfo : function() {
    			return {
    				//кто виноват в том, что всё это сделал
    			};
    		}
    	});
    	tinymce.PluginManager.add('rikkisocialicons', tinymce.plugins.RikkiSocialIconsPlugin);
    })();
    


    Добавление одной кнопки, которая обрамляет выделенный фрагмент текста каким-тегом нашего shortcode выглядит так:

    ed.addCommand('mce-blogspot', function() {
        var newcontent = '[userid type="blogspot"]' + tinyMCE.activeEditor.selection.getContent({format : 'raw'}) + '[/userid]';
        tinyMCE.activeEditor.selection.setContent(newcontent);
    });
    ed.addButton('blogspot', {
        title : 'blogspot',
        сmd : 'mce-blogspot',
        image : url + '/img/blogspot.gif'
    });
    


    Разумеется, пользователь, уже привыкший к тому, что наш плагин сам себя настраивает, захочет попытаться сгенерировать кнопки в цикле:
    (function() {
    	tinymce.create('tinymce.plugins.RikkiSocialIconsPlugin', {
    	var newButtons = ["ljuser", "ljcomm", "liruman", "lirugirl", "ljr", "vk", "twitter"];
    	tinymce.create('tinymce.plugins.LjusersPlugin', {
    		init : function(ed, url) {
    			var newButtonsLength = newButtons.length, i = 0;
    			while(i < newButtonsLength){
    				var itemTitle = newButtons[i];
    				var itemCommand = 'mce'+itemTitle;
    				ed.addCommand(itemCommand, function() {
    					var newcontent = '[userid type="'+itemTitle+'"]' + tinyMCE.activeEditor.selection.getContent({format : 'raw'}) + '[/userid]';
    					tinyMCE.activeEditor.selection.setContent(newcontent);
    				});
    				ed.addButton(itemTitle, {
    					title : itemTitle,
    					cmd : itemCommand,
    					image : url + '/img/'+itemTitle+'.gif'
    				});
    				i++;
    			}
    		},
    		getInfo : function() {
    			return {
    				longname : 'Rikki\'s WP Social Icons',
    				author : 'Rikki Mongoose',
    				authorurl : 'http://rikkimongoose.ru',
    				infourl : 'http://rikkimongoose.ru/projects/rikkis-wp-social-icons/',
    				version : "1.0"
    			};
    		}
    	});
    	tinymce.PluginManager.add('rikkisocialicons', tinymce.plugins.RikkiSocialIconsPlugin);
    })();
    


    И то верно — разве не должны за программиста работать роботы?

    Если сгенерировать кнопки таким образом, а потом перейти на editor, то сразу почувствуешь гордость за своё мастерство. Кнопки, указанные в array-е, выстроились в ряд, и на каждой — та самая иконка, которую увидит посетитель сайта или читатель RSS-ленты. Очень удобно!

    Этот код выглядит замечательно, но у него есть один-единственный недостаток — он не работает. Это первый баг, который подстерегает вас в tinyMCE. На первый взгляд кнопочки выглядят самыми обыкновенными — но если пощёлкать по ним, то оказывается, что каждая из них вставляет shortcode с последним элементом — в нашем случае с twitter.

    Придётся писать всё руками через замыкания, как подсказывают в комментах. Примерно вот так:

    (function() {
    	var newButtons = ["ljuser", "google_plus", "wordpress", "habrahabr", "github"];
    	tinymce.create('tinymce.plugins.RikkiSocialIconsPlugin', {
    	init : function(ed, url) {
    		for ( i in newButtons) {
    			var itemTitle = newButtons[i];
    			(function(itemTitle) {
    				var itemCommand = 'mce'+itemTitle;
    				ed.addCommand(itemCommand, function() {
    					var newcontent = '[userid type="'+itemTitle+'"]' + tinyMCE.activeEditor.selection.getContent({format : 'raw'}) + '[/userid]';
    					tinyMCE.activeEditor.selection.setContent(newcontent);
    				});
    				ed.addButton(itemTitle, {
    					title : itemTitle,
    					cmd : itemCommand,
    					image : url + '/img/'+itemTitle+'.gif'
    				});
    			})(itemTitle);
    		}
    	},
    		getInfo : function() {
    			return {
    				longname : 'Rikki\'s WP Social Icons',
    				author : 'Rikki Mongoose',
    				authorurl : 'http://rikkimongoose.ru',
    				infourl : 'http://rikkimongoose.ru/projects/rikkis-wp-social-icons/',
    				version : "1.0"
    			};
    		}
    	});
    	tinymce.PluginManager.add('rikkisocialicons', tinymce.plugins.RikkiSocialIconsPlugin);
    })();
    


    Тестирование показало, что этот вариант отлично работает. Слава комментаторам!

    Этот вариант уже намного лучше — каждая кнопка знает своё место и срабатывает, как надо.

    Наконец, есть ещё второй баг, который зависит исключительно от вас. И, хотя Wordpress, tinyMCE и даже Internet Explorer тут виноваты разве что в не очень удобной обработке ошибки, он может попортить вам немало крови.

    Возможная ошибка касается функции tinymce.PluginManager.add(param1, param2). Пожалуйста, пишите её очень внимательно.

    param1 должен совпадать с ключом, в который мы добавляли массив id новых кнопок в нашем php-файле. В моём случае там должен быть 'rikkisocialicons' (т.к. в PHP у нас $plugin_array['rikkisocialicons']).
    param2 должен совпадать с тем, что создаётся через tinymce.create(). В моём случае это tinymce.plugins.RikkiSocialIconsPlugin, (т.к. в скрипте у нас написано tinymce.create('tinymce.plugins.RikkiSocialIconsPlugin')

    Если что-то из этого не будет совпадать — у вашего editor-а пропадёт панель с кнопками, а консоль сообщит про ошибку 'k is undefined', которая произошла… разумеется, в сжатой jQuery.min.js, так что ни отладить, ни посмотреть вызов не будет ни малейшей возможности.

    Как добавить иконку для сервиса X?


    Сначала неплохо сходить на GitHub и посмотреть — вдруг уже появился fork с нужной иконкой? Если нет — тогда спасаемся своими руками.
    1. Сохраняем её в формате gif в ту же директорию, где лежат остальные иконки
    2. Добавляем в $options ещё один параметр, где ключ совпадает с именем gif-а, а URL — с url-ом, который нужно подставлять, причём имя пользователя заменяем на %s (пользуясь случаем, передаю привет всем C++ программистам, которые на этом месте наверняка испытают ностальгию).
    3. Если хотите кнопку идёте в JavaScript — добавляете элемент с тем же id в массив newButtons
    4. Упаковываете всё в ZIP
    5. Отключаете и удаляете старую версию плагина. Не беспокойтесь, shortcode в ваших постах при этом не пострадают
    6. Загружаете обновлённую версию, активируете её и наслаждаетесь


    Очерёдность кнопок зависит от очерёдности в PHP-файле.

    Вот и всё!



    Плагин лежит в каталоге Wordpress. Там же, или на GitHub-е, вы можете порадовать автора, сообщив, что теперь и ваш stand-alone блог украшен модными иконками. А ещё можете дополнить проект иконками dreamwidth-а или ещё какой-нибудь xanga.

    Добавляем в каталог



    Остался последний шаг — добавление плагина в каталог Wordpress, чтобы его можно было найти стандартным поиском.

    Разумеется, нужно не забыть добавить предварительно readme.txt и стандартные комментарии в заголовке плагина. Иначе пользователь не сможет узнать, что же он ставит.

    Вся процедура подробно описана в большом англоязычном мануале.

    А для тех, кому не терпится — вот короткое пошаговое руководство:

    1. Идём на wordpress.org и создаём там учётную запись
    2. Получаем пароль. Заходим под ним и идём на страницу добавления plugin-а. Закидываем туда ссылку на ZIP, пишем название и описание, и отправляем Post.
    3. Теперь нужно подождать. Плагин из мануала рассматривали 18 часов, с моим уложились в часа 4. Когда рассмотрение закончилось и всё получилось, на сервере появится пустая SVN-директория, в которую надо будет залить ваш проект
    4. Создаём локально папку, делаем туда checkout и копируем наш plug-in в trunc. Сжимать ZIP-ом не надо — после commit-а в trunc скрипт на сервере сожмёт всё автоматически.
    5. Делаем commit.
    6. Идём на страницу http://wordpress.org/extend/plugins/rikkis-wp-social-icons/. Вместо rikkis-wp-social-icons подставьте название вашего плагина.
    7. Идём на страницу http://wordpress.org/extend/plugins/ и смотрим внимательно на список Newest Plugins. Вот он, наш красавец!


    Если что-то не заработало — обратитесь к большому мануалу. Там всё очень подробно расписано.

    См. также


    1. В каталоге Wordpress
    2. Официальное представительство на GitHub
    3. Скачать c GitHub
    4. Оригинальная статья от Jenyay — часть 1, часть 2, часть 3.


    Автор будет благодарен всем, кто возьмёт шефство над github-овской версией проекта и будет развивать его дальше.
    Поделиться публикацией

    Комментарии 50

      +24
      <img scr="http://www.google.com/s2/favicons?domain={$domain}" />
      


      Та-да!
        +7
        Совести у вас нет, человек старался, писал код, а тут вы со своим Та-да…
          +1
          Нормальное решение, кстати. Только почти для любой livejournal-подобной блогплатформы не подходит. Livejournal, dreamwidth и Li.ru — там на favicon-ах не человечки
          +1
          Тоже хорошее решение!

          Но не очень гибкое — например, у Ли.рушечки два эккаунта, для мальчиков и для девочек.
            +3
            Вам шашечки или ехать? :) Я думал что суть в
            показывать рядом с ником человека, на которого я ссылаюсь, ещё и значок его сервиса"
            а не статус аккаунта…
              +2
              Мне значок нужен — человечек из ЖЖ, например.
                –1
                Вам нужно — вы и делайте. Суть плагина не в этом же.

                Ну честное слово, люди, имейте капельку уважения к автору.
                  +6
                  Есть проблема — автор — это я и есть :)
          +2
          Аaaaa! читайте про замыкания в JS и вам не понадобиться эта простыня.
          init : function(ed, url) {
              for ( i in newButtons) {
                  var itemTitle = newButtons[i];
                  (function(itemTitle) {
                      var itemCommand = 'mce'+itemTitle;
                      ed.addCommand(itemCommand, function() {
                          var newcontent = '[userid type="'+itemTitle+'"]' + tinyMCE.activeEditor.selection.getContent({format : 'raw'}) + '[/userid]';
                              tinyMCE.activeEditor.selection.setContent(newcontent);
                          });
                      ed.addButton(itemTitle, {
                          title : itemTitle,
                          cmd : itemCommand,
                          image : url + '/img/'+itemTitle+'.gif'
                      });
                  })(itemTitle);
              }
          }
          
            0
            И к стати все со стандартным способом передачи строк в JS должно работать просто надо это повесить на
            admin_print_scripts-post-php в данном случае
              +2
              И еще, я не понимаю зачем нужен github, если плагин уже есть в svn wordpress-а с кучей плюшек типа автоапдейта на сайтах и прочее?
                +2
                Потому что писалось сперва локально и надо было где-то хранить изменения.

                Недописанный огрызок кода в директорию wordpress-а не возьмут.
                  +1
                  Я обычно пишу код в дропбоксе, а когда он уже рабочий, отправляю на wordpress.org
                    0
                    С git-ом оно ещё проще — можно делать коммиты локально, без синхронизации.
                    \
                    Другое дело, что когда я заканчивал код, я ещё не знал, куда его правильно заливать.
                      +1
                      С svn-ом тоже проще, можно делать коммиты в ветку (branches), без синхронизации ;) а когда наберётся достаточно комитов, можно мерджить. Только вот мерджи в svn плохо работают когда более 1 человека на проекте, git лучше справляется с конфликтами :)
                    +3
                    В директорию WordPress.org возьмут любой недоработанный код, главное readme.txt правильно оформлять. На то и даётся вам репозиторий с директорией trunk :)
                  0
                  Во! Замыкание помогло :).
                    0
                    Когда в JS есть цикл и вместо нормальных последовательных значений вдруг всегда только последнее — значить нужно замыкание :)
                      0
                      Теперь оно точно на одну страницу влезет!
                        0
                        А передачу строк в JS лучше бы таки сделали — константы надо описывать один раз.
                          0
                          Способ нужный и полезный.

                          Однако в этом случае ещё важна возможность настройки — какие кнопки показывать, какие нет. Страшновато вставлять 12 кнопочек сразу.
                    0
                    Внёс изменение в статью. Что не отменяет того факта, что в tinyMCE — баг :)
                    0
                    10500 кнпочек в редакторе ради вставки картинки, вы шутите? Одна кнопка которая вставляет ссылку, а дальше уже идет ее разбор на принадлежность к платформе.

                    Ну и CSS объявить описать классом и выводить в заголовок, с возможностью отключения.
                      +4
                      Здорово, спасибо за статью! Есть немного замечаний :)

                      * Класс «socialusers» и шорткод «userid» могут вызвать конфликт с другими плагианами, которые решили свои классы и шорткоды так же назвать. Берите более уникальные называния, например с префиксом: Rikki_Social_Users и [rikki_userid]. Было много примеров где знаменитые плагины были вынуждены менять свои шорткоды.

                      * Функция add_shortcode появилась в WordPress 2.5. Если вы не собираетесь поддерживать WordPress 2.4, вам не нужно проверять её наличие с function_exists.

                      * Callback функции в объекте класса нужно было передавать по ссылке в php 4, а в php 5 все объекты по умолчанию будут передаваться по ссылке, поэтому можно писать array( $this, 'mce_buttons' ); без &.

                      * Не стоит забывать о безопасности. Пользователь может ввести что-нибудь вроде: foo.com' onclick='...' в вместо URL, и ваш код подставит его XSS в вашу ссылку, даже если он не имеет право публиковать unfiltered_html. В данном случае нужно esc_url(). Подробнее: codex.wordpress.org/Data_Validation

                      Вообще идея клёвая, но я бы не стал её реализовывать таким образом. Я бы лучше добавил 10 своих иконок (а скорее даже один спрайт) + 10 правил в CSS, и в постах добавлял бы класс:

                      <a href="http://twitter.com/kovshenin" class="twitter">kovshenin</a>
                      


                      И если вдруг плагин сломался, перестал поддерживаться автором, не совместим с WordPress версией 3.6, или же просто надоел, то все мои существующие посты будут всё равно отображаться, пусть без иконок, но всё же не придётся бегать по базе и искать все [userid.

                      Ещё раз спасибо за статью!

                      P.S. WordPress правильнее писать с заглавной буквы P: capitalp.org/
                        +1
                        Спасибо на за подробный разбор.

                        > * Класс «socialusers» и шорткод «userid» могут вызвать конфликт с другими плагианами, которые решили свои классы и шорткоды так же назвать. Берите более уникальные называния, например с префиксом: Rikki_Social_Users и [rikki_userid]. Было много примеров где знаменитые плагины были вынуждены менять свои шорткоды.
                        ==

                        Это верно. Всё изучение особенностпей плагиностроения заняло 1 день, так что о стандарте я заботился по минимуму.

                        > * Функция add_shortcode появилась в WordPress 2.5. Если вы не собираетесь поддерживать WordPress 2.4, вам не нужно проверять её наличие с function_exists.

                        Это проверяется. См. строчку:

                        if (!function_exists ('add_shortcode') ) return;

                        > * Callback функции в объекте класса нужно было передавать по ссылке в php 4, а в php 5 все объекты по умолчанию будут передаваться по ссылке, поэтому можно писать array( $this, 'mce_buttons' ); без &.

                        Это для совместимости с версиями Wordpress младше 3.2 Gershwin, которые ещё могли работать на PHP4. Не знаю динамики, но возможно где-то ещё остались пользователи старых версий (3.2 вышла год назад).

                        > * Не стоит забывать о безопасности. Пользователь может ввести что-нибудь вроде: foo.com' onclick='...' в вместо URL, и ваш код подставит его XSS в вашу ссылку, даже если он не имеет право публиковать unfiltered_html. В данном случае нужно esc_url(). Подробнее: codex.wordpress.org/Data_Validation

                        Надо будет поставить. Хотя есть элемент свободы — плагины всё равно можно ставить только на standalone, и если блоггеру угодно ломать свой standalone через XSS — это его право :).

                        Plugin так или иначе поставляется в виде исходника, так что для пользователя, который искушён в XSS, не будет особой проблемой подправить исходники, чтобы убрать esc_url.

                        > И если вдруг плагин сломался, перестал поддерживаться автором, не совместим с WordPress версией 3.6, или же просто надоел, то все мои существующие посты будут всё равно отображаться, пусть без иконок, но всё же не придётся бегать по базе и искать все [userid].

                        Это тоже хорошая идея. И вполне можно бы было ограничиться JS-дополнением — от PHP требовалось бы только инциализировать кнопки.

                        Но опасаюсь конфликта с CSS-настройками текущей theme. Мало ли, что у них на уме.

                        > P.S. WordPress правильнее писать с заглавной буквы P

                        Это да. Впрочем, и статьи надо писать днём, а не в час ночи, как я :). Тогда и буква P будет на месте.
                          +2
                          Рад что мой ответ принёс какую-то пользу =)

                          > Это проверяется. См. строчку:
                          > if (!function_exists ('add_shortcode') ) return;

                          Прочитайте внимательно ещё раз: «Если вы не собираетесь поддерживать WordPress 2.4, вам не нужно проверять её наличие с function_exists.» :)

                          > Не знаю динамики, но возможно где-то ещё остались пользователи старых версий (3.2 вышла год назад).

                          Статистику можете посмотреть здесь: wordpress.org/about/stats/ и обратите внимание на версии PHP — 99.5% версии 5 и выше. Поддерживать всё, что ниже двух последних версий не стоит, ровно так же как и IE6 ;) А если всё же вас кто-нибудь спросит насчёт поддержки 3.1, вы можете смело попросить их обновиться.

                          > Надо будет поставить. Хотя есть элемент свободы — плагины всё равно можно ставить только на standalone, и если блоггеру угодно ломать свой standalone через XSS — это его право :).

                          Защита в данном случае от пользователей которые не имеют доступ к FTP или админ-права на сайте. Если бы все standalone сайты имели только одного пользователя админ, то проблем бы не возникало, но у нас ведь есть ещё и авторы, и редакторы, и контрибюторы, и за счёт плагинов может быть ещё масса ролей без права на unfiltered_html, как в оригинальном WordPress, так и в сетевом варианте WordPress Multisite.

                          Короче говоря, это не «элемент свободы» а дырка :) и как только пользователи на неё пожалуются на WordPress.org, ваш плагин оттуда исчезнет, так что поторопитесь :)

                          > Но опасаюсь конфликта с CSS-настройками текущей theme. Мало ли, что у них на уме.

                          Да, вы правы, нужен снова префикс, rikki-twitter :)
                            0
                            Код подправил. Посмотрите — нормально?
                              +1
                              Да, так гораздо лучше :)
                                0
                                На wordpress svn и в github я тоже обновил. Так что смело устаскивайте в свой fork обновления :)
                              0
                              >обратите внимание на версии PHP — 99.5% версии 5 и выше. Поддерживать всё, что ниже двух последних версий не стоит, ровно так же как и IE6 ;) А если всё же вас кто-нибудь спросит насчёт поддержки 3.1, вы можете смело попросить их обновиться.

                              ну, изредка появляются товарищи на php4. Были просьбы сделать плагин совместимым, потому как хостинг такой
                                +1
                                Таким товарищам нужно помогать. Помогать выбирать хороший и безопасный хостинг. Ровно так же, как и помогать заказчикам переходить на более свежие версии IE, нежели писать поддержку IE6 в своих работах.
                                  0
                                  Сложно сказать что лучше для меня, для пользователя или для кармы. Чуток подправить обьявление классов и ссылки или решать чужие проблемы? А с IE6 я делаю просто — если платят ( причем за IE6 как за всех остальных ) я делаю, хотя таких уже не осталось. Те что были со старыми версиями сидели не от хорошей жизни.
                                    0
                                    > Чуток подправить обьявление классов и ссылки

                                    Если писать для php4, то нужен весь цикл, нужно установить те версии WordPress, которые работают под php4 и нужно установить сам интерпрератор и всё протестировать от начала и до конца. Только после этого, можно будет говорить о совместимости.

                                    А если вы поставили пару амперсандов и думаете, что оно всё заработает на php4 и WordPress 3.1, то вы ошибаетесь :)
                                      0
                                      Всё это напоминает дикие игры с Python 2/3. Половина примеров в сети — для 2.x, половина — для 3.x. В итоге приходится держать в голове 2 языка со всеми эквивалентами.
                                        –1
                                        делать мне больше нечего как ставить php4
                                        плагин у товарища работает, с дебуг флагом никаких варнингов нет — и ладушки

                                        Тут для network activation все менялось в последних версиях — вот это засада. Конструкция уже выглядит ужасающе.
                                          +1
                                          > делать мне больше нечего как ставить php4

                                          Вот это всё и объясняет :)
                                            –1
                                            Гы, я рад за вас.
                                            Может приведете хоть один резон за php4?
                                              +1
                                              Ха! Я изначально написал, что поддерживать WordPress 3.1 и особенно PHP4 не стоит, на что вы ответили:

                                              > ну, изредка появляются товарищи на php4. Были просьбы сделать плагин совместимым, потому как хостинг такой

                                              Поэтому если вы собираетесь делать поддержку PHP4 и WordPress 3.1, то нужно делать её правильно, а не тяп-ляп, что некогда даже интерпретатор установить =)

                                              > Может приведете хоть один резон за php4?

                                              В отличии от вас, нет. :)
                                                –1
                                                WP с 2.9 до 3.4.1 у меня как раз стоят, причем в двух вариантах, а работоспособность в php4 просто фича, которая лично мне не интересна, но почему-то нужна некоторой публике и достигнута она была очень просто и без ущерба для функциональности.
                          +1
                          За одно ближе к вечеру будет проведен краш-тест сайту lurkmore.to на сопротивление хабраэффекту :)
                            +3
                            А нечего было про WordPress гадости писать ;)
                            0
                            К стати забыл написать, что лучше проверять наличие инстанса класса прежде чем обьявлять класс и реализовывать. Я уже не помню когда и где, но были проблемы с тем что WP в каких-то случаях плагин инклюдит несколько раз.
                              0
                              В версии 2.0 — будет :)
                                0
                                При загрузке плагинов wp-settings.php использует include_once: core.trac.wordpress.org/browser/tags/3.3.1/wp-settings.php#L194 поэтому при нормальных обстоятелствах, файл будет исполнен только один раз.

                                Если вдруг ваш плагин подгрузился два раза за один запрос, значит что-то работает не правильно, где-то допустили ошибку, и нужно в этом разобраться, а не просто class_exists поставить. :)

                                Функция class_exists здесь будет полезена в том случае, если у вашего класса не совсем уникальное имя, как например «socialusers» или же, если ваш плагин является частью темы или другого, более крупного плагина, как например Jetpack и Polldaddy, Grunion Contact Form, WP-Stats, и т.д.
                                  0
                                  >при нормальных обстоятелствах

                                  а еще бывают ненормальные :) это было где-то между версиями 2.9 и 3.2
                                  мне лень смотреть когда это было и почему, а вот вставить пару проверок в шаблон плагина — это полезно
                                    0
                                    Не нужно обвинять WordPress, если вы воспользовались криво-написанным плагином или темой :) Ещё в версии 1.5 плагины загружались с include_once(). Вряд ли «где-то между версиями 2.9 и 3.2» это изменилось на include() а потом обратно.

                                    core.trac.wordpress.org/browser/tags/1.5/wp-settings.php#L111

                                    Поправьте меня, если я не прав :)
                                      0
                                      Плагины еще активируются с хуком, а еще бывает крон и аякс, и еще в разных местах wp_load инклюдиться
                                      В принципе конечно это разные скопы, иначе ломается все, но если в конструкторе что-то делать зависимое от состояния то можно влететь

                                      Но самый простой случай — это просто сдублировать файл плагина :) У меня один заказчик старые копии зачем-то в подпапку складировал.
                                        0
                                        Активируются плагины особым образом — опять же через редирект, чтобы если вдруг плагин сломанный, весь WordPress у вас не сломался.

                                        core.trac.wordpress.org/browser/tags/3.4.1/wp-admin/includes/plugin.php#L533

                                        WP_Cron запускается в отдельном HTTP запросе, даже если выставлен ALTERNATE_WP_CRON, то происходит всего лишь редирект. Никакой магии нет. И admin-ajax.php в этом плане ничем не отличается, wp-load.php он и в африке wp-load.php :)

                                        А вот ваш «простой случай» всё объясняет, но WordPress тут, извините, абсолютно не причём. И не важно, версия 2.9 или 3.2 :)
                                          0
                                          Ваши инициалы случайно не К.О.? :)

                                          Я лично наблюдал фишку с двойным вызовом с ошибками переопределения класса и так далее. Каким именно макаром оно там сдвоилось я без понятия.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое